package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"encoding/json"
	"math/big"
	"strings"
	"fmt"
	pb "github.com/hyperledger/fabric/protos/peer"
	"errors"
	"time"
)

type CrowFundChaincode struct {
}

type CrowFund struct {
	Owner		string 		`json:"owner"`				// 发起人
	CreateTime  int64	 	`json:"createTime"`			// 创建时间

	TargetToken	string 		`json:"targetToken"`			// 所需币种

	SourceToken	string		`json:"sourceToken"`			// 众筹币种
	SourceNum   *big.Int		`json:"sourceNum"`			// 众筹额度

	EndTime		int64		`json:"endTime"`			// 结束时间

	ExchangedNum *big.Int		`json:"exchangedNum"`		// 已兑换数量（需要的币种）
	Balance 	*big.Int		`json:"balance"`		// 众筹余额
}

func main() {
	err := shim.Start(new(CrowFundChaincode))
	if err != nil {
		fmt.Print(err.Error())
	}
}

var log *shim.ChaincodeLogger

func (t CrowFundChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

	return shim.Success(nil)
}

func (t CrowFundChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

	log = getLogger(stub)
	fcn, args := stub.GetFunctionAndParameters()
	if fcn == "" {
		return shim.Error("function is nil")
	} else if fcn == "add" {
		return t.Add(stub, args)
	} else if fcn == "exchange" {
		return t.Exchange(stub, args)
	} else if fcn == "end" {
		return t.End(stub, args)
	} else if fcn == "del" {
		return t.Del(stub, args)
	}

	return shim.Error(fmt.Sprintf("function '%s' not fund", fcn))
}

func (t CrowFundChaincode) Add(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	log.Infof("invoke add, args: %s", args)
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	// owner, createTime, TargetToken, SourceToken, SourceNum, endTime

	var crowfund CrowFund
	err := json.Unmarshal([]byte(args[0]), &crowfund)
	if err != nil {
		return shim.Error(err.Error())
	} else {
		if crowfund.Owner == "" {
			return shim.Error("owner is nil")
		}
		if crowfund.CreateTime <= 0 {
			return shim.Error("create time is nil")
		}
		if crowfund.TargetToken == "" {
			return shim.Error("target token is nil")
		}
		if !checkValue(*crowfund.SourceNum) {
			return shim.Error("source num is 0")
		}
		if crowfund.SourceToken == "" {
			return shim.Error("source token is nil")
		}
		if crowfund.EndTime <= 0 {
			return shim.Error("end time is nil")
		}
	}

	// 数据初始化
	crowfund.TargetToken = strings.ToLower(crowfund.TargetToken)
	crowfund.SourceToken = strings.ToLower(crowfund.SourceToken)

	crowfund.Owner = has0xSuf(strings.ToLower(crowfund.Owner))

	// 检查地址合法性
	if !checkAddress(crowfund.Owner) {
		return shim.Error(addressError.Error())
	}
	
	crowfund.ExchangedNum = new(big.Int)
	crowfund.Balance = crowfund.SourceNum

	// 保存数据
	if err = setCrowfund(stub, crowfund); err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

type ExchangeTransaction struct {
	From   			string 		`json:"from"`
	To     			string 		`json:"to"`
	MainToken 		string 		`json:"mainToken"`
	MainValue  		*big.Int		`json:"mainValue"`
	ExchangeToken 	string 		`json:"exchangeToken"`
	ExchangeValue 	*big.Int		`json:"exchangeValue"`
}

func (t CrowFundChaincode) Exchange(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	log.Infof("args: %s", args)
	if len(args) != 1 {
		return shim.Error(parameterErr.Error())
	}
	// 获取参数
	//from, to, mainToken, mainValueStr, exchangeToken, exchangeValueStr
	//tokenName, from, to = strings.ToLower(tokenName), strings.ToLower(from), strings.ToLower(to)
	exTransByte := []byte(args[0])
	var exTrans ExchangeTransaction
	log.Info("开始反序列化参数")
	err := json.Unmarshal(exTransByte, &exTrans)
	if err != nil {
		return shim.Error(err.Error())
	} else {
		// 判断参数并将部分参数转小写操作
		if exTrans.From == "" {
			return shim.Error("exchange from is nil, please check")
		}
		if exTrans.To == "" {
			return shim.Error("exchange to is nil")
		}
		if exTrans.MainToken == "" {
			return shim.Error("exchange maintoken is nil")
		}
		if exTrans.ExchangeToken == "" {
			return shim.Error("exchange exchangeToken is nil")
		}
		if !checkValue(*exTrans.ExchangeValue) {
			return shim.Error(valueErr.Error())
		}
		if !checkValue(*exTrans.MainValue) {
			return shim.Error(valueErr.Error())
		}
	}

	exTrans.From = has0xSuf(strings.ToLower(exTrans.From))
	exTrans.To = has0xSuf(strings.ToLower(exTrans.To))

	// 检查地址合法性
	if !checkAddress(exTrans.From, exTrans.To) {
		return shim.Error(addressError.Error())
	}

	exTrans.MainToken = strings.ToLower(exTrans.MainToken)
	exTrans.ExchangeToken = strings.ToLower(exTrans.ExchangeToken)

	// 业务处理
	// 寻找数据
	key, err := stub.CreateCompositeKey(exTrans.To, []string{exTrans.MainToken, exTrans.ExchangeToken})
	log.Info(key)
	if err != nil {
		return shim.Error(err.Error())
	}
	log.Info("开始提取db数据")
	crowfund, err := getCrowfund(stub, key)
	if err != nil {
		return shim.Error(err.Error())
	}

	log.Info("db数据提取完成")

	// 如果结束时间已过，则禁止操作
	if crowfund.EndTime <= time.Now().Unix() {
		return shim.Error("crowfund has been closed")
	}
	// 判断众筹币余额
	if crowfund.Balance.Cmp(exTrans.ExchangeValue) < 0 {
		return shim.Error("insufficient balance of crow token")
	}

	// 扣减众筹币
	crowfund.Balance = new(big.Int).Sub(crowfund.Balance, exTrans.ExchangeValue)
	// 增加兑换币
	crowfund.ExchangedNum = new(big.Int).Add(crowfund.ExchangedNum, exTrans.MainValue)


	// 保存数据
	if err = setCrowfund(stub, *crowfund); err != nil {
		return shim.Error(err.Error())
	}
	if err = saveRecord(stub, exTrans); err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func (t CrowFundChaincode) End(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 3 {
		return shim.Error(parameterErr.Error())
	}

	owner, needToken, crowToken := has0xSuf(strings.ToLower(args[0])), strings.ToLower(args[1]), strings.ToLower(args[2])

	// 地址合法性校验
	if !checkAddress(owner) {
		return shim.Error(addressError.Error())
	}

	key, err := stub.CreateCompositeKey(owner, []string{needToken, crowToken})
	if err != nil {
		return shim.Error(err.Error())
	}
	crowfund, err := getCrowfund(stub, key)
	if err != nil {
		return shim.Error(err.Error())
	}

	// 因为需要用户地址去构建组合键，所以无需进行权限判定

	// 关闭
	crowfund.Balance = new(big.Int)

	err = setCrowfund(stub, *crowfund)
	if err != nil {
		return shim.Error(err.Error())
	}

	// 返回余额数据
	extrans := &ExchangeTransaction{
		From:          "",
		To:            crowfund.Owner,
		MainToken:     crowfund.TargetToken,
		MainValue:     crowfund.ExchangedNum,
		ExchangeToken: crowfund.SourceToken,
		ExchangeValue: crowfund.Balance,
	}
	eb, err := json.Marshal(extrans)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(eb)
}

func (t CrowFundChaincode) Del(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 3 {
		return shim.Error(parameterErr.Error())
	}

	owner, needToken, crowToken := has0xSuf(strings.ToLower(args[0])), strings.ToLower(args[1]), strings.ToLower(args[2])
	key, err := stub.CreateCompositeKey(owner, []string{needToken, crowToken})
	if err != nil {
		return shim.Error(err.Error())
	}
	err = stub.DelState(key)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(nil)
}

func getCrowfund(stub shim.ChaincodeStubInterface, key string) (*CrowFund, error) {
	cfb, err := stub.GetState(key)
	if err != nil {
		return nil, err
	}
	var crowfund CrowFund
	err = json.Unmarshal(cfb, &crowfund)
	return &crowfund, err
}

func setCrowfund(stub shim.ChaincodeStubInterface, crowfund CrowFund) error {
	key, err := stub.CreateCompositeKey(crowfund.Owner, []string{crowfund.TargetToken, crowfund.SourceToken})
	if err != nil {
		return err
	}
	cfb, err := json.Marshal(crowfund)
	if err != nil {
		return err
	}
	return stub.PutState(key, cfb)
}

/**
	保存众筹记录
 */
func saveRecord(stub shim.ChaincodeStubInterface, extrans ExchangeTransaction) error {
	key, err := stub.CreateCompositeKey(transactionKeyPre, []string{extrans.To, extrans.MainToken, extrans.ExchangeToken})
	if err != nil {
		return err
	}
	exb, err := json.Marshal(extrans)
	if err != nil {
		return err
	}
	return stub.PutState(key, exb)
}

var transactionKeyPre = "transaction"

var parameterErr = errors.New("parameter err")

var nonceError = errors.New("nonce error")
var balanceErr = errors.New("insufficient balance")
var valueErr = errors.New("does not suppert this transaction")


var zero = new(big.Int).SetInt64(1000000000000000000)

func has0xSuf(addr string) string {
	if strings.Index(addr, "0x") != 0 {
		return addr
	}
	return addr[2:]
}

func getLogger(stub shim.ChaincodeStubInterface) (c *shim.ChaincodeLogger) {
	fcn, _ := stub.GetFunctionAndParameters()
	c = shim.NewLogger(fmt.Sprintf("%s.%s.%s", stub.GetChannelID(), "crowfund", fcn))
	c.SetLevel(shim.LogDebug)
	return
}


func checkAddress(addrs ...string) bool {
	for i := range addrs {
		if len(has0xSuf(addrs[i])) != 40 {
			return false
		}
	}
	return true
}

var addressError = errors.New("address error")

func checkValue(value big.Int) bool {
	if value.Cmp(new(big.Int)) <= 0 {
		return false
	}
	valStr := value.String()
	if len(valStr) >= 21 {
		return false
	}
	return true
}